package com.atguigu.filter;

import com.alibaba.fastjson.JSONObject;
import com.atguigu.result.RetVal;
import com.atguigu.result.RetValCodeEnum;
import com.atguigu.util.IpUtil;
import org.apache.http.HttpHeaders;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpCookie;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.nio.charset.StandardCharsets;
import java.util.List;

@Component
public class AccessFilter implements GlobalFilter {

    //匹配路径对象
    private AntPathMatcher antPathMatcher=new AntPathMatcher();
    @Autowired
    private RedisTemplate redisTemplate;
    @Value("${filter.whiteList}")
    private String filterWhiteList;
    /**
     * @param exchange 服务网络交换器，存放着重要的请求-响应属性、请求实例和响应实例
     *                 不可变实例 如果我们想要修改它，需要通过mutate()方法生成一个新的实例
     * @param chain    网关过滤的链表，用于过滤器的链式调用 设计模式:责任链模式
     *                 面试问题:谈谈你对设计模式的理解--->单例，代理，工厂，装饰模式
     */
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        //1.对于规定的内部接口 不允许外部调用
        ServerHttpRequest request = exchange.getRequest();
        String path = request.getURI().getPath();
        //如果请求路径当中以sku开头就让他没有权限访问
        if(antPathMatcher.match("/sku/**", path)){
            //写一些数据到浏览告诉访问端 没有权限访问
            return writeDataToBrowser(exchange,RetValCodeEnum.NO_PERMISSION);
        }
        //2.如果用户登录了拿到用户id判断该用户是否为同一IP
        String userId=getUserId(request);
        //获取用户的临时id
        String userTempId=getUserTempId(request);

        if("-1".equals(userId)){
            return writeDataToBrowser(exchange,RetValCodeEnum.NO_PERMISSION);
        }
        //3.对于指定的某些(我的订单/我的购物车/我xxx)资源,必须登录
        if(antPathMatcher.match("/order/**", path)){
            if(StringUtils.isEmpty(userId)){
                //写一些数据到浏览告诉访问端 没有权限访问
                return writeDataToBrowser(exchange,RetValCodeEnum.NO_LOGIN);
            }
        }
        //4.用户请求白名单 如果请求是白名单 就直接跳转到登录页面
        for(String filterWhite:filterWhiteList.split(",")){
            //用户访问路径包含白名单路径，并且用户未登录
            if(path.indexOf(filterWhite)!=-1&&StringUtils.isEmpty(userId)){
                //跳转到登录页面
                ServerHttpResponse response = exchange.getResponse();
                response.setStatusCode(HttpStatus.SEE_OTHER);
                response.getHeaders().set(HttpHeaders.LOCATION,"http://passport.gmall.com/login.html?originalUrl="+request.getURI());
                return response.setComplete();
            }
        }
        //5.把用户id需要保存到header中 传给shop-web那边的request
        if(!StringUtils.isEmpty(userId)||!StringUtils.isEmpty(userTempId)){
            if(!StringUtils.isEmpty(userId)){
                //将用户id放入header当中
                request.mutate().header("userId",userId).build();
            }
            if(!StringUtils.isEmpty(userTempId)){
                request.mutate().header("userTempId",userTempId).build();
            }
            //过滤器放开拦截器 让下游请求继续执行(此时修改了exchange对像)
            return chain.filter(exchange.mutate().request(request).build());
        }

        //过滤器放开拦截器 让下游请求继续执行
        return chain.filter(exchange);
    }

    private String getUserTempId(ServerHttpRequest request) {
        //从header和cookie中拿用户的token
        List<String> headerValueList = request.getHeaders().get("userTempId");
        String userTempId=null;
        if(!CollectionUtils.isEmpty(headerValueList)){
            userTempId=headerValueList.get(0);
        }else{
            HttpCookie cookie = request.getCookies().getFirst("userTempId");
            if(cookie!=null){
                userTempId=cookie.getValue();
            }
        }
        return userTempId;
    }

    /**
     * 如果要拿到用户id,首先需要拿到用户的token
     */
    private String getUserId(ServerHttpRequest request) {
        //从header和cookie中拿用户的token
        List<String> headerValueList = request.getHeaders().get("token");
        String token=null;
        if(!CollectionUtils.isEmpty(headerValueList)){
            token=headerValueList.get(0);
        }else{
            HttpCookie cookie = request.getCookies().getFirst("token");
            if(cookie!=null){
                token=cookie.getValue();
            }
        }
        if(!StringUtils.isEmpty(token)){
            //拿到token之后如何拿到用户的id 拼接key(user:login:token)从redis中拿取用户信息
            String userKey="user:login:"+token;
            String loginInfoJson =(String) redisTemplate.opsForValue().get(userKey);
            JSONObject loginInfoObject = JSONObject.parseObject(loginInfoJson);
            String loginIp = loginInfoObject.getString("loginIp");
            //拿到当前请求的ip地址
            String requestIpAddress = IpUtil.getGatwayIpAddress(request);
            if(loginIp.equals(requestIpAddress)){
                return loginInfoObject.getString("userId");
            }else{
                return "-1";
            }
        }
        return null;
    }

    //把数据写给浏览器
    private Mono<Void> writeDataToBrowser(ServerWebExchange exchange,RetValCodeEnum retValCodeEnum) {
        ServerHttpResponse response = exchange.getResponse();
        RetVal<Object> retVal=RetVal.build(null, retValCodeEnum);
        //把该retVal转换为json字符串
        byte[] retBytes = JSONObject.toJSONString(retVal).getBytes(StandardCharsets.UTF_8);
        //将字节转换为一个数据buffer 效率高
        DataBuffer dataBuffer = response.bufferFactory().wrap(retBytes);
        //设置返回给浏览器的请求头 数据类型是json字符串
        response.getHeaders().add("Content-Type","application/json;charset=UTF-8");
        //把数据发射给浏览器
        return response.writeWith(Mono.just(dataBuffer));
    }
}
